using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;

namespace DeferredShading
{
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        private GraphicsDeviceManager m_graphics;

        private Model m_modelToDraw;

        private Effect m_effect;

        private RenderTarget2D m_renderTarget1;
        private RenderTarget2D m_renderTarget2;
        
        private Texture2D m_diffuseTex;
        
        private VertexBuffer m_vBuffer;
        private short[] m_indexList;
        private IndexBuffer m_iBuffer;

        private RenderTargetBinding[] m_gBufferTargets;

        private readonly Vector3 CameraPosition = new Vector3(0.0f, 2.0f, 5.0f);
        private readonly double FieldOfView = 70.0;

        private int m_windowWidth = 1024;
        private int m_windowHeight = 1024;

        private bool m_renderGBuffers = true;

        public Game1()
        {
            m_graphics = new GraphicsDeviceManager(this);
            m_graphics.PreferredBackBufferWidth = m_windowWidth;
            m_graphics.PreferredBackBufferHeight = m_windowHeight;
            Content.RootDirectory = "Content";
        }

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            // TODO: Add your initialization logic here

            base.Initialize();
        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            // Load Content.
            m_effect = Content.Load<Effect>("Effects/Effect1");
            m_modelToDraw = Content.Load<Model>("Models/Sphere");
            m_diffuseTex = Content.Load<Texture2D>("Textures/texture");

            // Remap Models.
            RemapModel(m_modelToDraw, m_effect);

            m_vBuffer = new VertexBuffer(m_graphics.GraphicsDevice, typeof(VertexPositionNormalTexture), 6, BufferUsage.None);
            VertexPositionNormalTexture[] vdata = new VertexPositionNormalTexture[4];

            vdata[0].Position = new Vector3(-1.0f, -1.0f, 0.0f);
            vdata[0].Normal = new Vector3(0.0f, 0.0f, 1.0f);
            vdata[0].TextureCoordinate = new Vector2(0.0f, 1.0f);

            vdata[1].Position = new Vector3(-1.0f, 1.0f, 0.0f);
            vdata[1].Normal = new Vector3(0.0f, 0.0f, 1.0f);
            vdata[1].TextureCoordinate = new Vector2(0.0f, 0.0f);

            vdata[2].Position = new Vector3(1.0f, 1.0f, 0.0f);
            vdata[2].Normal = new Vector3(0.0f, 0.0f, 1.0f);
            vdata[2].TextureCoordinate = new Vector2(1.0f, 0.0f);

            vdata[3].Position = new Vector3(1.0f, -1.0f, 0.0f);
            vdata[3].Normal = new Vector3(0.0f, 0.0f, 1.0f);
            vdata[3].TextureCoordinate = new Vector2(1.0f, 1.0f);

            m_vBuffer.SetData<VertexPositionNormalTexture>(vdata);
            m_indexList = new short[6] { 0, 1, 2, 0, 2, 3 };
            m_iBuffer = new IndexBuffer(m_graphics.GraphicsDevice, IndexElementSize.SixteenBits, 6, BufferUsage.None);
            m_iBuffer.SetData<short>(m_indexList);

            m_gBufferTargets = new RenderTargetBinding[2];

            m_renderTarget1 = new RenderTarget2D(m_graphics.GraphicsDevice, m_windowWidth, m_windowHeight, true, SurfaceFormat.Vector4, DepthFormat.Depth24, 0, RenderTargetUsage.DiscardContents);
            m_renderTarget2 = new RenderTarget2D(m_graphics.GraphicsDevice, m_windowWidth, m_windowHeight, true, SurfaceFormat.Vector4, DepthFormat.Depth24, 0, RenderTargetUsage.DiscardContents);

            m_gBufferTargets[0] = m_renderTarget1;
            m_gBufferTargets[1] = m_renderTarget2;
        }

        /// <summary>
        /// Call this method to remap the model.
        /// </summary>
        /// <param name="a_model">The model to remap.</param>
        /// <param name="a_effect">The effect to be used by the model.</param>
        private static void RemapModel(Model a_model, Effect a_effect)
        {
            foreach (ModelMesh mesh in a_model.Meshes)
            {
                foreach (ModelMeshPart part in mesh.MeshParts)
                {
                    part.Effect = a_effect;
                }
            }
        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {
            // TODO: Unload any non ContentManager content here
        }

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="a_gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime a_gameTime)
        {
            // Allows the game to exit
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape) == true)
                this.Exit();

            if (Keyboard.GetState().IsKeyDown(Keys.G) == true)
            {
                m_renderGBuffers = true;
            }
            else if (Keyboard.GetState().IsKeyDown(Keys.F) == true)
            {
                m_renderGBuffers = false;
            }

            base.Update(a_gameTime);
        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="a_gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime a_gameTime)
        {
            GraphicsDevice.Clear(Color.Black);

            DrawBuffer();

            DrawToScreen(a_gameTime);


            if (m_renderGBuffers == true)
            {
                //m_graphics.GraphicsDevice.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.DarkSlateBlue, 1.0f, 0);
                using (SpriteBatch sprite = new SpriteBatch(m_graphics.GraphicsDevice))
                {
                    sprite.Begin(SpriteSortMode.BackToFront, BlendState.Opaque, SamplerState.PointClamp, null, null, null);
                    sprite.Draw(m_renderTarget1, new Vector2(0, 0), null, Color.White, 0.0f, new Vector2(0, 0), 0.25f, SpriteEffects.None, 1.0f);
                    sprite.Draw(m_renderTarget2, new Vector2(256, 0), null, Color.White, 0.0f, new Vector2(0, 0), 0.25f, SpriteEffects.None, 1.0f);
                    sprite.End();
                }
            }

            base.Draw(a_gameTime);
        }

        private void DrawBuffer()
        {
            float aspectRatio = m_graphics.GraphicsDevice.Viewport.AspectRatio;

            Matrix projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians((float)FieldOfView), aspectRatio, 10.0f, 1000.0f);
            Matrix view = Matrix.CreateLookAt(new Vector3(0.0f, 50.0f, 100.0f), Vector3.Zero, Vector3.Up);

            m_graphics.GraphicsDevice.SetRenderTargets(m_gBufferTargets);

            GraphicsDevice.Clear(Color.Transparent);

            for (int i = 0; i < 100; i++)
            {
                Matrix world = Matrix.CreateTranslation(50 - i % 10 * 10.0f, 0.0f, -80 + (i / 10 * 12.0f)) * Matrix.CreateScale(2.0f);

                foreach (ModelMesh mesh in m_modelToDraw.Meshes)
                {
                    foreach (Effect e in mesh.Effects)
                    {
                        e.Parameters["Projection"].SetValue(projection);
                        e.Parameters["View"].SetValue(view);
                        e.Parameters["World"].SetValue(world);
                        e.CurrentTechnique = e.Techniques["OOC"];
                    }
                    mesh.Draw();
                }
            }
            m_graphics.GraphicsDevice.SetRenderTargets(null);
        }

        private void DrawToScreen(GameTime a_gameTime)
        {
            m_graphics.GraphicsDevice.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.Green, 1.0f, 0);

            m_effect.Parameters["World"].SetValue(Matrix.Identity);
            m_effect.Parameters["View"].SetValue(Matrix.CreateTranslation(new Vector3(0.0f, 0.0f, 1.0f)));
            m_effect.Parameters["Projection"].SetValue(Matrix.Identity);
            m_effect.Parameters["position"].SetValue(m_renderTarget1);
            m_effect.Parameters["normal"].SetValue(m_renderTarget2);

            m_effect.CurrentTechnique = m_effect.Techniques["PostProcess"];

            m_graphics.GraphicsDevice.SetVertexBuffer(m_vBuffer);
            m_graphics.GraphicsDevice.Indices = m_iBuffer;

            foreach (EffectPass pass in m_effect.CurrentTechnique.Passes)
            {
                pass.Apply();
                m_graphics.GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, 4, 0, 2);
            }
        }
    }

}